﻿//
// UserRepository.cs
//
// Authors:
//    Lasse Andreas Hauge Petersen <09547@iha.dk>
//    Søren Flohr Nielsen <10638@iha.dk>
//    Claus Jørgensen <10229@iha.dk>
//
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.Linq.Expressions;
using System.Data.Linq;
using System.Data.Objects;
using System.Security.Cryptography;
using System.Diagnostics.Contracts;

namespace RSD.Data
{
    /// <summary>
    /// Represents a User Repository.
    /// </summary>
    [ContractClass(typeof(Contracts.IUserRepositoryContract))]
    public interface IUserRepository : IRepository<User>
    {
        /// <summary>
        /// Gets a user by username and password.
        /// </summary>
        /// <param name="username">The username.</param>
        /// <param name="password">The password.</param>
        /// <returns>A user with matching credentials; otherwise <c>null</c>.</returns>
        User GetUserByNameAndPassword(string username, string password);

        /// <summary>
        /// Generates a new user.
        /// </summary>
        /// <param name="username">The username.</param>
        /// <param name="password">The password.</param>
        /// <param name="realname">The realname.</param>
        /// <returns>The generated user.</returns>
        User GenerateUser(string username, string password, string realname);
    }

    /// <summary>
    /// Represents a User Repository.
    /// </summary>
    public sealed class UserRepository : RepositoryBase<User>, IUserRepository
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="UserRepository"/> class.
        /// </summary>
        /// <param name="context">The object context.</param>
        public UserRepository(IObjectContext context)
            : base(context)
        {
            Contract.Requires(context != null);
        }

        /// <summary>
        /// Generates a 64 byte salt.
        /// </summary>
        /// <returns></returns>
        public static byte[] GenerateSalt()
        {
            Contract.Ensures(Contract.Result<byte[]>() != null);

            var data = new byte[64];
            var rngp = new RNGCryptoServiceProvider();

            rngp.GetBytes(data);

            return data;
        }

        /// <summary>
        /// Computes the hash value for the specified password and salt.
        /// </summary>
        /// <param name="password">The password.</param>
        /// <param name="salt">The salt.</param>
        /// <returns>The computed hash code.</returns>
        public static byte[] ComputeHash(string password, byte[] salt)
        {
            Contract.Requires(!string.IsNullOrEmpty(password));
            Contract.Requires(salt != null);
            Contract.Ensures(Contract.Result<byte[]>() != null);

            if (string.IsNullOrEmpty(password))
            {
                throw new ArgumentNullException("password");
            }

            if (salt == null)
            {
                throw new ArgumentNullException("salt");
            }


            var bytes = salt.Concat(Encoding.Default.GetBytes(password)).ToArray();

            using (var sha512 = SHA512.Create())
            {
                return sha512.ComputeHash(bytes);
            }
        }

        #region IUserRepository Members

        /// <summary>
        /// Retrieves a User by his username and matching password.
        /// </summary>
        /// <param name="username">The User's username.</param>
        /// <param name="password">The User's password.</param>
        /// <returns>
        /// Returns the first matching user; otherwise <c>null</c>.
        /// </returns>
        public User GetUserByNameAndPassword(string username, string password)
        {
            var lookupUser = FindBy(u => u.Username == username).FirstOrDefault();

            if (lookupUser == null || lookupUser.Salt == null)
            {
                return null;
            }

            var hashedPassword = ComputeHash(password, lookupUser.Salt);

            if ((lookupUser.Password != null) &&
                (lookupUser.Password.SequenceEqual(hashedPassword)))
            {
                return lookupUser;
            }

            return null;
        }

        /// <summary>
        /// Creates a new user with a given username and password.
        /// </summary>
        /// <param name="username">The User's username.</param>
        /// <param name="password">The User's password.</param>
        /// <param name="realname">The User's real name.</param>
        /// <returns>The created user.</returns>
        public User GenerateUser(string username, string password, string realname)
        {
            var salt = GenerateSalt();

            return new User()
            {
                Username = username,
                Realname = realname,
                Password = ComputeHash(password, salt),
                Salt = salt
            };
        }

        #endregion
    }
}
